
/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package no.nordicsemi.support.v18.scanner;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.bluetooth.ble.BleCentralManager;
import ohos.bluetooth.ble.BleCentralManagerCallback;
import ohos.bluetooth.ble.BleScanFilter;
import ohos.bluetooth.ble.BleScanResult;
import ohos.event.intentagent.IntentAgent;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.InnerEvent;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.logging.Handler;

public class BluetoothScannerImplV5 extends BluetoothLeScannerCompat {
    private final Map<ScanCallback, ScanCallbackWrapper> wrappers = new HashMap<>();
    private long powerSaveRestInterval;
    private long powerSaveScanInterval;
    @Nullable
    private EventHandler handlerThread;
    @Nullable
    private BleCentralManager centralManager;
    private List<BleScanFilter> filters;

    @Override
    protected void startScanInternal(Context context, List<BleScanFilter> scanFilters
            ,ScanSettings scanSettings, ScanCallback callback, EventHandler handler) {
        synchronized (wrappers) {
            if (wrappers.containsKey(callback)) {
                throw new IllegalArgumentException("scanner already started with given scanCallback");
            }
            final ScanCallbackWrapper wrapper = new ScanCallbackWrapper(
                    false, false,
                    scanFilters, scanSettings, callback, handler);
            wrappers.put(callback, wrapper);
        }
        centralManager = new BleCentralManager(context, new ScanCallbackV5());
        filters = new ArrayList<BleScanFilter>();
        centralManager.startScan(filters);
        setPowerSaveSettings();
    }

    @Override
    void stopScanInternal(ScanCallback callback) {
        boolean shouldStop;
        ScanCallbackWrapper wrapper;
        synchronized (wrappers) {
            wrapper = wrappers.remove(callback);
            shouldStop = wrappers.isEmpty();
        }
        if (wrapper == null)
            return;
        wrapper.close();
        setPowerSaveSettings();
        if (shouldStop) {
            if (handlerThread != null) {
                handlerThread = null;
            }
        }
        setPowerSaveSettings();
    }

    @Override
    void startScanInternal(List<BleScanFilter>filters, ScanSettings settings
            ,Context context, IntentAgent callbackIntent) {
        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
                .withDeviceId("")
                .withBundleName(context.getBundleName())
                .withAbilityName("no.nordicsemi.support.v18.scanner.ScannerService")
                .build();
        intent.setParam(ScannerService.EXTRA_SETTINGS, settings);
        intent.setParam(ScannerService.EXTRA_PENDING_INTENT, callbackIntent);
        intent.setParam(ScannerService.EXTRA_START, true);
        intent.setOperation(operation);
        context.startAbility(intent, 0);
    }

    @Override
    void stopScanInternal(Context context, IntentAgent callbackIntent) {
    }

    @Override
    public void flushPendingScanResults(ScanCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null!");
        }
        ScanCallbackWrapper wrapper;
        synchronized (wrappers) {
            wrapper = wrappers.get(callback);
        }
        if (wrapper == null) {
            throw new IllegalArgumentException("callback not registered!");
        }
        wrapper.flushPendingScanResults();
    }

    @Override
    public ArrayList<ScanResult> flushPendingScanResults(List<ScanResult> nativeScanResults) {
        if (nativeScanResults == null) {
            throw new IllegalArgumentException("callback cannot be null!");
        }
        ScanCallbackWrapper wrapper;
        synchronized (wrappers) {
            wrapper = wrappers.get(nativeScanResults);
        }
        if (wrapper == null) {
            throw new IllegalArgumentException("callback not registered!");
        }
        wrapper.flushPendingScanResults();
        return null;
    }

    public class ScanCallbackV5 implements BleCentralManagerCallback {
        List<ScanResult> results = new ArrayList<ScanResult>();

        @Override
        public void scanResultEvent(BleScanResult resultCode) {
            synchronized (wrappers) {
                for (ScanResult scanResult : results) {
                    BleScanResult temp = (BleScanResult) scanResult.getDevice();
                    if (temp.getPeripheralDevice().getDeviceAddr()
                            .equals(resultCode.getPeripheralDevice().getDeviceAddr())) {
                        return;
                    }
                }
                ScanResult scanResult = fromNativeScanResult(resultCode);
                results.add(scanResult);
                final Collection<ScanCallback> scanCallbackWrappers = wrappers.keySet();
                for (final ScanCallback wrapper : scanCallbackWrappers) {
                    wrapper.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, scanResult);
                }
                for (final ScanCallback wrapper : scanCallbackWrappers) {
                    wrapper.onBatchScanResults(results);
                }
            }
        }

        @Override
        public void scanFailedEvent(int resultCode) {
            synchronized (wrappers) {
                final Collection<ScanCallback> scanCallbackWrappers = wrappers.keySet();
                for (final ScanCallback wrapper : scanCallbackWrappers) {
                    wrapper.onScanFailed(resultCode);
                }
            }
        }

        @Override
        public void groupScanResultsEvent(final List<BleScanResult> scanResults) {
        }

        ScanResult fromNativeScanResult(BleScanResult nativeScanResult) {
            return new ScanResult(nativeScanResult,
                    ScanRecord.parseFromBytes(nativeScanResult.getRawData()), nativeScanResult.getRssi(), nativeScanResult.getTime());
        }
    }

    private final Runnable powerSaveSleepTask = new Runnable() {
        @Override
        public void run() {
            if (centralManager != null && powerSaveRestInterval > 0 && powerSaveScanInterval > 0) {
                centralManager.stopScan();
                handlerThread.sendEvent(InnerEvent.get(powerSaveScanTask), powerSaveRestInterval);
            }
        }
    };
    private final Runnable powerSaveScanTask = new Runnable() {
        @Override
        public void run() {
            if (centralManager != null && powerSaveRestInterval > 0 && powerSaveScanInterval > 0) {
                centralManager.startScan(filters);
                handlerThread.sendEvent(InnerEvent.get(powerSaveSleepTask), powerSaveScanInterval);
            }
        }
    };

    private void setPowerSaveSettings() {
        long minRest = Long.MAX_VALUE, minScan = Long.MAX_VALUE;
        synchronized (wrappers) {
            for (final ScanCallbackWrapper wrapper : wrappers.values()) {
                final ScanSettings settings = wrapper.scanSettings;
                if (settings.hasPowerSaveMode()) {
                    if (minRest > settings.getPowerSaveRest()) {
                        minRest = settings.getPowerSaveRest();
                    }
                    if (minScan > settings.getPowerSaveScan()) {
                        minScan = settings.getPowerSaveScan();
                    }
                }
            }
        }
        if (minRest < Long.MAX_VALUE && minScan < Long.MAX_VALUE) {
            powerSaveRestInterval = minRest;
            powerSaveScanInterval = minScan;
            if (handlerThread != null) {
                handlerThread.removeTask(powerSaveScanTask);
                handlerThread.removeTask(powerSaveSleepTask);
                handlerThread.sendEvent(InnerEvent.get(powerSaveSleepTask), powerSaveScanInterval);
            }
        } else {
            powerSaveRestInterval = powerSaveScanInterval = 0;
            if (handlerThread != null) {
                handlerThread.removeTask(powerSaveScanTask);
                handlerThread.removeTask(powerSaveSleepTask);
            }
        }
    }
}
